#ifndef __CONNECT_TO_H__
#define __CONNECT_TO_H__

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define FD_STAT_FLAG_CONNECTING     0x0001
#define FD_STAT_FLAG_CONNECTED      0x0002
#define FD_STAT_FLAG_READABLE       0x0004
#define FD_STAT_FLAG_WRITEABLE      0x0010
#define FD_STAT_FLAG_NOMORE_READ    0x0020
#define FD_STAT_FLAG_NOMORE_WRITE   0x0040
#define SETNONBLOCK(fd) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK)
#define SETNODELAY(fd)  do { int turn = 1; setsockopt(fd,SOL_TCP,TCP_NODELAY,(void*)&turn,sizeof(turn)); } while(0)
#define LOCAL_LOGERR(fmt, ...)  do { \
    printf("%s %-32s line %-4d "fmt, __FILE__, __func__, __LINE__, ##__VA_ARGS__); \
    fflush(stdout); \
} while(0)

static inline int set_tcp_keepAlive(int fd, int start, int interval, int count)
{
    int turn = 1;
    if (fd < 0 || start < 0 || interval < 0 || count < 0) return -1;  //入口参数检查 ，编程的好惯。
    //启用心跳机制
    if(setsockopt(fd,SOL_SOCKET,SO_KEEPALIVE,(void*)&turn,sizeof(turn)) == -1) { perror("setsockopt"); return -1; }
    //启用心跳机制开始到首次心跳侦测包发送之间的空闲时间
    if(setsockopt(fd,SOL_TCP,TCP_KEEPIDLE,(void *)&start,sizeof(start)) == -1) { perror("setsockopt"); return -1; }
    //两次心跳侦测包之间的间隔时间
    if(setsockopt(fd,SOL_TCP,TCP_KEEPINTVL,(void *)&interval,sizeof(interval)) == -1) { perror("setsockopt"); return -1; }
    //探测次数，即将几次探测失败判定为TCP断开
    if(setsockopt(fd,SOL_TCP,TCP_KEEPCNT,(void *)&count,sizeof(count)) == -1) { perror("setsockopt"); return -1; }
    return 0;
}

static inline int connect_to(const char *host, uint16_t port, int epoll_fd, int* fd_stat, int l4_protocol, int setkeepalive, int clamp)
{
    int fd = -1;
    int ret = -1;
    struct addrinfo *result = NULL, *rp;
    struct addrinfo hints;
    int errno2 = 0;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET; //AF_UNSPEC;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = l4_protocol == 17 ? SOCK_DGRAM : SOCK_STREAM;

    char port_str[64];
    sprintf(port_str, "%u", port);
    int s = getaddrinfo(host, port_str, &hints, &result); if (s != 0) { LOCAL_LOGERR("GETADDRINFO ERROR [%s]\n", gai_strerror(s)); return -1; }
    if (result == NULL) { LOCAL_LOGERR("GETADDRINFO ERROR [%s]\n", gai_strerror(s)); return -1; }
    rp = result;
    fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd == -1) { LOCAL_LOGERR("fail to call socket[%m]\n"); goto out; }
    if (l4_protocol == 17) {
    } else {
        if (clamp > 0) setsockopt(fd, SOL_SOCKET, TCP_WINDOW_CLAMP, (char *)&clamp, sizeof(clamp));
        SETNODELAY(fd);
        if (setkeepalive > 0) {
            set_tcp_keepAlive(fd, 120, 30, 3);
        }
    }
    if (epoll_fd >= 0) {
        SETNONBLOCK(fd);
        struct epoll_event ev;
        ev.events = EPOLLET | EPOLLIN | EPOLLOUT | EPOLLERR;
        ev.data.fd = fd;
        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev)) { LOCAL_LOGERR("epoll ctrol add error.\n"); exit(1); }
    }
    ret = connect(fd, rp->ai_addr, rp->ai_addrlen);
    errno2 = errno;
out:
    if (result) freeaddrinfo(result);
    if (ret < 0) {
        if (errno2 == EINPROGRESS) {
            if (fd_stat) *fd_stat = FD_STAT_FLAG_CONNECTING;
            return fd;
        }
        LOCAL_LOGERR("connect to %s:%u ERROR[%m]\n", host, port);
        close(fd);
        return -1;
    }
    if (fd_stat) *fd_stat = FD_STAT_FLAG_CONNECTED;
    return fd;
}

static inline int port_to_listen_fd(const char *host, uint16_t port)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { LOCAL_LOGERR("fail to call socket\n"); return -1; }
    int len = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&len, sizeof(int));
    //int clamp = 65535; setsockopt(fd, SOL_SOCKET, TCP_WINDOW_CLAMP, (char *)&clamp, sizeof(clamp));
    struct sockaddr_in sockaddr; memset(&sockaddr, 0, sizeof(sockaddr));
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(port);
    sockaddr.sin_addr.s_addr = INADDR_LOOPBACK;
    if (host && host[0]) inet_pton(AF_INET, host, &sockaddr.sin_addr.s_addr);
    if (bind(fd, (struct sockaddr *)&sockaddr, sizeof(struct sockaddr)) < 0) { LOCAL_LOGERR("BIND PORT[%u] ERROR[%m]\n", port); close(fd); return -1; }
    if (listen(fd, 100) < 0) { LOCAL_LOGERR("LISTEN PORT[%u] ERROR[%m]\n", port); close(fd); return -1; }
    //LOCAL_LOGERR("BIND PORT %u OK\n", port);
    int i = 1;
    setsockopt(fd, SOL_SOCKET ,SO_REUSEADDR, &i,sizeof(i));
    return fd;
}

static inline int readline(int fd, char *buf)
{
    int len = 0;
    char c;
    while (read(fd, &c, 1) == 1) {
        if (c == '\n') break;
        buf[len] = c;
        len++;
    }
    return len;
}

#endif
